from contextlib import contextmanager
from sys import stderr

from khronos.utils.misc import clock, indent

class Debugger(object):
    def __init__(self, outfile=stderr):
        self.indent = 0
        self.outfile = outfile
        
    @contextmanager
    def block(self, block_name):
        """Handy old-fashioned message printing debug method. Just put a block you want to know 
        when was entered and finished inside this context manager using a with statement.
        Example (assuming 'debug' is a Debugger object):
            def my_func(x, y):
                with debug.block("foo"):
                    print "oooo"
            
            >>> my_func()
            {Debug} --> block foo
            oooo
            {Debug} block foo --> (XXX seconds)"""
        self.outfile.write(indent("{Debug} --> block %s" % (block_name,), self.indent))
        self.outfile.write("\n")
        self.outfile.flush()
        start = clock()
        self.indent += 1
        yield
        self.indent -= 1
        self.outfile.write(indent("{Debug} block %s --> (%f seconds)" % 
                                  (block_name, clock() - start), self.indent))
        self.outfile.write("\n")
        self.outfile.flush()
        
    def function(self, fnc):
        """Function decorator. Does almost the same as debug block, but prints the name of the 
        function being called and also its arguments and return value."""
        def new_fnc(*args, **kwargs):
            args_repr   = ", ".join(repr(arg) for arg in args)
            kwargs_repr = ", ".join("%s=%r" % (arg, val) for arg, val in kwargs.iteritems())
            sep         = "" if len(args) == 0 or len(kwargs) == 0 else ", "
            self.outfile.write(indent("{Debug} --> %s(%s%s%s)" % 
                                      (fnc.__name__, args_repr, sep, kwargs_repr), self.indent))
            self.outfile.write("\n")
            self.outfile.flush()
            start = clock()
            self.indent += 1
            result = fnc(*args, **kwargs)
            self.indent -= 1
            self.outfile.write(indent("{Debug} %s() --> %r (%f seconds)" % 
                                      (fnc.__name__, result, clock() - start), self.indent))
            self.outfile.write("\n")
            self.outfile.flush()
            return result
        new_fnc.__name__ = fnc.__name__
        new_fnc.__doc__ = fnc.__doc__
        return new_fnc
        
    def message(self, *parts):
        """This just prints a message to the file associated with the debugged object."""
        text = " ".join(str(p) for p in parts)
        self.outfile.write(indent("{Debug} %s" % (text,), self.indent))
        self.outfile.write("\n")
        self.outfile.flush()
        
debug = Debugger()
